package com.freecycle.sg.sqlite;

import java.util.Arrays;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

import com.freecycle.sg.sqlite.Freebie.Freebies;
import com.freecycle.sg.sqlite.Freebie.ReferenceData;
import com.freecycle.sg.sqlite.Freebie.Users;
import com.freecycle.sg.sqlite.FreebieDatabase.Tables;
import com.freecycle.sg.utils.SelectionBuilder;

public class FreebieProvider extends ContentProvider {

	private static final String TAG = "FreebieProvider";

	private FreebieDatabase mOpenHelper;

	private static final UriMatcher sUriMatcher = buildUriMatcher();

	/**
	 * Constants used to differentiate between URI requests
	 */
	private static final int REFERENCE_DATA = 101; // request for all reference  data
	private static final int REFERENCE_DATA_ID = 102; // request for reference
														// data with specified
														// ID

	private static final int USERS = 201;
	private static final int USERS_ID = 202;
	private static final int USERS_ID_FREEBIES = 203;

	private static final int FREEBIES = 301; // request for all messages
	private static final int FREEBIES_ID = 302; // request for message with
												// specified ID

	/**
	 * Build and return a {@link UriMatcher} that catches all {@link Uri}
	 * variations supported by this {@link ContentProvider}.
	 */
	private static UriMatcher buildUriMatcher() {
		final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
		final String authority = Freebie.CONTENT_AUTHORITY;

		matcher.addURI(authority, "reference_data", REFERENCE_DATA);
		matcher.addURI(authority, "reference_data/*", REFERENCE_DATA_ID);

		matcher.addURI(authority, "users", USERS);
		matcher.addURI(authority, "users/*", USERS_ID);
		matcher.addURI(authority, "users/*/freebies", USERS_ID_FREEBIES);

		matcher.addURI(authority, "freebies", FREEBIES);
		matcher.addURI(authority, "freebies/*", FREEBIES_ID);

		return matcher;
	}

	@Override
	public boolean onCreate() {
		final Context context = getContext();
		mOpenHelper = new FreebieDatabase(context);
		return true;
	}

	/** {@inheritDoc} */
	@Override
	public String getType(Uri uri) {
		final int match = sUriMatcher.match(uri);
		switch (match) {
		case REFERENCE_DATA:
			return ReferenceData.CONTENT_TYPE;
		case REFERENCE_DATA_ID:
			return ReferenceData.CONTENT_ITEM_TYPE;
		case USERS:
			return Users.CONTENT_TYPE;
		case USERS_ID:
			return Users.CONTENT_ITEM_TYPE;
		case USERS_ID_FREEBIES:
			return Freebies.CONTENT_TYPE;
		case FREEBIES:
			return Freebies.CONTENT_TYPE;
		case FREEBIES_ID:
			return Freebies.CONTENT_ITEM_TYPE;
		default:
			throw new UnsupportedOperationException("Unknown uri: " + uri);
		}
	}

	/** {@inheritDoc} */
	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		Log.v(TAG,
				"querying (uri=" + uri + ", proj="
						+ Arrays.toString(projection) + ", selection="
						+ selection + ", selectionArgs="
						+ Arrays.toString(selectionArgs) + ", sortOrder="
						+ sortOrder + ")");
		final SQLiteDatabase db = mOpenHelper.getReadableDatabase();

		final int match = sUriMatcher.match(uri);
		switch (match) {
		default:
			// Most cases are handled with simple SelectionBuilder
			final SelectionBuilder builder = buildExpandedSelection(uri, match);
			return builder.where(selection, selectionArgs).query(db,
					projection, sortOrder);
		}
	}

	/** {@inheritDoc} */
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		Log.v(TAG, "inserting (uri=[" + uri + "], values=[" + values.toString() + "])");
		final SQLiteDatabase db = mOpenHelper.getWritableDatabase();

		final int match = sUriMatcher.match(uri);
		switch (match) {
		/**
		 * Need to build special ContentValues object for insertion on each
		 * table
		 */
		case REFERENCE_DATA: {
			db.insertOrThrow(Tables.REFERENCES, null, values);
			return ReferenceData.buildReferenceDataUri(values
					.getAsString(ReferenceData.REF_DATA_TYPE));
		}
		case USERS: {
			db.insertOrThrow(Tables.USERS, null, values);
			return Users.buildUserUri(values.getAsString(Users.USER_ID));
		}
		case FREEBIES: {
			db.insertOrThrow(Tables.FREEBIES, null, values);
			return Freebies.buildFreebieUri(values
					.getAsString(Freebies._ID));
		}
		default: {
			throw new UnsupportedOperationException(
					"Cannot insert data into database. Unknown uri: " + uri);
		}
		}
	}
	
	/** {@inheritDoc} */
	@Override
	public int bulkInsert(Uri uri, ContentValues[] values) {
		int noOfRecords = values.length;
		
		Log.v(TAG, "inserting bulk of " + noOfRecords
				+ " records to database (uri=[" + uri + "]");
		// Caution: If number of {@link ContentValues} aka records is bigger than
		// 1000, Android OS will throw error related to HeapWorker

		final SQLiteDatabase db = mOpenHelper.getWritableDatabase();

		final int match = sUriMatcher.match(uri);
		switch (match) {
		case REFERENCE_DATA: {
			db.beginTransaction();
			for (ContentValues value : values) {
				db.insertOrThrow(Tables.REFERENCES, null, value);
			}
			db.setTransactionSuccessful();
			db.endTransaction();
			return noOfRecords;
		}
		case USERS: {
			db.beginTransaction();
			for (ContentValues value : values) {
				db.insertOrThrow(Tables.USERS, null, value);
			}
			db.setTransactionSuccessful();
			db.endTransaction();
			return noOfRecords;
		}
		case FREEBIES: {
			db.beginTransaction();
			for (ContentValues value : values) {
				db.insertOrThrow(Tables.FREEBIES, null, value);
			}
			db.setTransactionSuccessful();
			db.endTransaction();
			return noOfRecords;
		}
		default: {
			throw new UnsupportedOperationException(
					"Cannot insert bulk of data into database. Unknown uri: "
							+ uri);
		}
		}
	}

	/** {@inheritDoc} */
	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		Log.v(TAG,
				"updating (uri=[" + uri + "], values=[" + values.toString()
						+ "], selection=[" + selection + "], selectionArgs=["
						+ Arrays.toString(selectionArgs) + "])");
		final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
		final SelectionBuilder builder = buildSimpleSelection(uri);
		return builder.where(selection, selectionArgs).update(db, values);
	}

	/** {@inheritDoc} */
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		Log.v(TAG, "deleting (uri=[" + uri + "], selection=[" + selection
				+ "], selectionArgs=[" + Arrays.toString(selectionArgs) + "])");
		final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
		final SelectionBuilder builder = buildSimpleSelection(uri);
		return builder.where(selection, selectionArgs).delete(db);
	}

	/**
	 * Build a simple {@link SelectionBuilder} to match the requested
	 * {@link Uri}. This is usually enough to support {@link #insert},
	 * {@link #update}, and {@link #delete} operations.
	 */
	private SelectionBuilder buildSimpleSelection(Uri uri) {
		final SelectionBuilder builder = new SelectionBuilder();
		final int match = sUriMatcher.match(uri);
		switch (match) {
		case REFERENCE_DATA: {
			return builder.table(Tables.REFERENCES);
		}
		case REFERENCE_DATA_ID: {
			final String referenceDataType = uri.getPathSegments().get(1);
			return builder.table(Tables.REFERENCES)
					.where(ReferenceData.REF_DATA_TYPE + "=?",
							referenceDataType);
		}
		case USERS: {
			return builder.table(Tables.USERS);
		}
		case USERS_ID: {
			final String userId = uri.getPathSegments().get(1);
			return builder.table(Tables.USERS).where(Users.USER_ID + "=?",
					userId);
		}
		case FREEBIES: {
			return builder.table(Tables.FREEBIES);
		}
		case FREEBIES_ID: {
			final String messageId = uri.getPathSegments().get(1);
			return builder.table(Tables.FREEBIES).where(
					Freebies.FREEBIE_ID + "=?", messageId);
		}
		default: {
			throw new UnsupportedOperationException("Unknown uri: " + uri);
		}
		}
	}

	/**
	 * Build an advanced {@link SelectionBuilder} to match the requested
	 * {@link Uri}. This is usually only used by {@link #query}, since it
	 * performs table joins useful for {@link Cursor} data.
	 */
	private SelectionBuilder buildExpandedSelection(Uri uri, int match) {
		final SelectionBuilder builder = new SelectionBuilder();
		switch (match) {
		case REFERENCE_DATA: {
			return builder.table(Tables.REFERENCES);
		}
		case REFERENCE_DATA_ID: {
			final String referenceType = ReferenceData.getReferenceType(uri);
			return builder.table(Tables.REFERENCES).where(
					ReferenceData.REF_DATA_TYPE + "=?", referenceType);
		}
		case USERS: {
			return builder.table(Tables.USERS);
		}
		case USERS_ID: {
			final String userId = Users.getUserId(uri);
			return builder.table(Tables.USERS).where(Users.USER_ID + "=?",
					userId);
		}
		case FREEBIES: {
			return builder.table(Tables.FREEBIES);
		}
		case FREEBIES_ID: {
			final String messageId = Freebies.getFreebieId(uri);
			return builder.table(Tables.FREEBIES).where(
					Freebies._ID + "=?", messageId);
		}
		default: {
			throw new UnsupportedOperationException("Unknown uri: " + uri);
		}
		}
	}

}
